package edu.siu.math.egut.util;

import java.io.Serializable;
import java.util.Map;
import java.util.WeakHashMap;

// TODO test the basic functionality of this class.
// TODO change Unipotent and Toral so that they operate with these instead of Polynomials...
// TODO improve this class by storing the denominator in a factored form and being more intelligent 
// about the common denominators, etc. 

/**
 * @author Joseph Hundley
 *
 */
public final class RationalFunction implements Serializable{

    private static final long serialVersionUID = 134L;

    private Polynomial numerator;
    private Polynomial denominator;
 
    public static RationalFunction update(RationalFunction r){
	return new RationalFunction( Polynomial.update(r.numerator),
		Polynomial.update(r.denominator));
	
    }
    public RationalFunction(Polynomial numerator, Polynomial denominator) {
	if(denominator == Polynomial.create(0))
	    throw new RuntimeException("Attempt to initialize a rational function with a denominator of zero.");
	this.numerator = numerator;
	this.denominator = denominator;
    }
    public RationalFunction(Polynomial numerator) {
	this.numerator = numerator;
	this.denominator = Polynomial.create(1);
    }
    public RationalFunction() {
	this.numerator = Polynomial.create(0);
	this.denominator = Polynomial.create(1);
    }
    public RationalFunction(int i) {
	this.numerator = Polynomial.create(i);
	this.denominator = Polynomial.create(1);
    }
    public RationalFunction(Monomial m) {
	this.numerator = Polynomial.create(m);
	this.denominator = Polynomial.create(1);
    }
    public RationalFunction(String s) {
	this.numerator = Polynomial.create(s);
	this.denominator = Polynomial.create(1);
    }
    public RationalFunction(String s, String t) {
	this.numerator = Polynomial.create(s);
	this.denominator = Polynomial.create(t);
    }
    public RationalFunction(int i, String t) {
	this.numerator = Polynomial.create(i);
	this.denominator = Polynomial.create(t);
    }
    public RationalFunction(int i, Monomial m) {
	this.numerator = Polynomial.create(i);
	this.denominator = Polynomial.create(m);
    }

   public RationalFunction plus(RationalFunction r){
	
	
	return new RationalFunction(this.numerator.times(r.denominator).plus(
		this.denominator.times(r.numerator)), 
		this.denominator.times(r.denominator));
    }
   public RationalFunction plus(Polynomial p){
       return this.plus(new RationalFunction(p));
   }
   public RationalFunction plus(Monomial m){
       return this.plus(new RationalFunction(m));
   }
   public RationalFunction plus(String s){
       return this.plus(new RationalFunction(s));
   }
   public RationalFunction plus(Integer i){
       return this.plus(new RationalFunction(i));
   }
   
   
   public RationalFunction times(RationalFunction r){
	
	
	return new RationalFunction(this.numerator.times(r.numerator), 
		this.denominator.times(r.denominator));
   }
   public RationalFunction times(Polynomial p){
       return this.times(new RationalFunction(p));
   }
   public RationalFunction times(Monomial m){
       return this.times(new RationalFunction(m));
   }
   public RationalFunction times(String s){
       return this.times(new RationalFunction(s));
   }
   public RationalFunction times(Integer i){
       return this.times(new RationalFunction(i));
   }
   
   public RationalFunction inverse(){
       return new RationalFunction(denominator, numerator);
       //constructor just called will throw a RuntimeException 
       // if numerator = 0.
   }
   
   public RationalFunction over( RationalFunction r){
       return new RationalFunction(this.numerator.times(r.denominator), 
		this.denominator.times(r.numerator));
       // runtime exception if r.numerator = 0.
       
   }
   public RationalFunction over( Polynomial p){
       return this.over(new RationalFunction(p));
   }
   public RationalFunction over( Monomial m){
       return this.over(new RationalFunction(m));
   }
   public RationalFunction over( String s){
       return this.over(new RationalFunction(s));
   }
   public RationalFunction over( int i){
       return this.over(new RationalFunction(i));
   }
   
   public RationalFunction raiseTo(int i){
       if( i== 0)
	   return new RationalFunction(1);
       else if(i <0)
	   return this.inverse().raiseTo(-i);
       else{
	   return new RationalFunction(numerator.raiseTo(i), denominator.raiseTo(i));
	   
       }
       
   }
   
   public Polynomial getNumerator() {
    return numerator;
}
public void setNumerator(Polynomial numerator) {
    this.numerator = numerator;
}
public Polynomial getDenominator() {
    return denominator;
}
public void setDenominator(Polynomial denominator) {
    this.denominator = denominator;
}
public String toString(){
       
       return "("+numerator.toString() +") / ("+denominator.toString()+")";
   }
   
   public RationalFunction replace( String s, Polynomial p) throws BadSubstitutionException{
       return new RationalFunction(numerator.replace(s, p), denominator.replace(s, p));
   }
   public RationalFunction replace( String s, RationalFunction r){
       if(numerator.degreeIn(s)> denominator.degreeIn(s))
	   return new RationalFunction(numerator.ratlFcnReplaceNum(s, r), 
		   denominator.ratlFcnReplaceNum(s, r).times(
		   r.denominator.raiseTo(numerator.degreeIn(s)-denominator.degreeIn(s))));
       else 
	   return new RationalFunction(numerator.ratlFcnReplaceNum(s, r).times(
		   r.denominator.raiseTo(denominator.degreeIn(s)-numerator.degreeIn(s))), 
		   denominator.ratlFcnReplaceNum(s, r));
       
	       }
	       
   public boolean equals(RationalFunction r){
       return this.numerator.times(r.denominator) == this.denominator.times(r.numerator);
   }
   public boolean isIndependentOf( String s){
       return this.numerator.isIndependentOf(s)&&this.denominator.isIndependentOf(s);
       //FIXME if the variable appears in numerator and denominator, but can be 
       // canceled, this methods gives bad info.  On the other hand, checking 
       // for cancellation is hard...
   }
public RationalFunction minus(RationalFunction argument) {
    
    return this.plus(argument.times(-1));
}
   
}
